package com.example;

import com.alibaba.fastjson.JSON;
import com.example.annotation.RequestMethod;
import com.example.annotation.type.Controller;
import com.example.annotation.RequestMapping;
import com.example.annotation.RequestParam;
import com.example.utils.AssertUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

public class DispatchServlet extends HttpServlet {

    private final Map<String, ServletMappingDefinition> handlerMapping = new ConcurrentHashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            String dispatch = doDispatch(req, resp);
            resp.getWriter().write(dispatch);
        } catch (Exception e) {
            resp.setContentType("text/html");
            StringBuilder sb = new StringBuilder();
            sb.append("<html><body>");
            sb.append("<h1>500 " + e.getMessage() + "</h1>");
            StackTraceElement[] stackTrace = e.getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                sb.append("<p style='padding-left:5px;'>" + stackTraceElement.toString() + "</p>");
            }
            sb.append("</body></html>");
            resp.getWriter().write(sb.toString());
        }
    }

    private String doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String url = request.getRequestURI();
        String contextPath = request.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
        if ("".equals(url)) {
            response.setStatus(200);
            return "<h1>hello world<h1>";
        }
        if (!this.handlerMapping.containsKey(url)) {
            response.setStatus(200);
            return "<h1>404 not found<h1>";
        }
        ServletMappingDefinition definition = handlerMapping.get(url);
        if (definition == null) {
            response.setStatus(404);
            return "<h1>404 not found</h1>";
        }
        String method = request.getMethod();
        if (!method.equals(definition.getRequestMethod().toString())) {
            response.setStatus(400);
            return "<h1>bad request</h1>";
        }
        Object[] values = ParameterDefinitionHandler.handlerParameter(definition.getMethod(), request, response);
        Object result = definition.getMethod().invoke(definition.getInstance(), values);
        if (definition.getMethod().getReturnType().equals(Void.TYPE)) {
            return "";
        } else {
            if (result instanceof String) return result.toString();
            if (result != null) {
                return JSON.toJSONString(result);
            }
        }
        return "";
    }

    @Override
    public void init(ServletConfig config) {
        doInitHandlerMapping();
    }

    private void doInitHandlerMapping() {
        ApplicationContext applicationContext = Application.getApplicationContext();
        if (applicationContext.getBeanDefinitionCount() == 0) {
            return;
        }
        for (Map.Entry<String, BeanWrapper> entry : applicationContext.getBeanDefinitionForAnnotation(Controller.class).entrySet()) {
            Class<?> clazz = entry.getValue().getWrapperClass();
            String baseUrl = "";
            if (clazz.isAnnotationPresent(RequestMapping.class)) {
                RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                baseUrl = "/" + requestMapping.value() + "/";
            }
            for (Method method : clazz.getMethods()) {
                if (method.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                    String url = (baseUrl + requestMapping.value()).replaceAll("/+", "/");
                    AssertUtils.isTrue(handlerMapping.get(url) == null,
                            String.format("class %s method %s mapped %s is repeat", clazz.getSimpleName(), method.getName(), requestMapping.value()));
                    ServletMappingDefinition definition = new ServletMappingDefinition();
                    definition.setMapping(url);
                    definition.setInstance(entry.getValue().getWrapperInstance());
                    definition.setMethod(method);
                    definition.setRequestMethod(requestMapping.method());
                    handlerMapping.put(url, definition);
                    String format = String.format("class %s method %s mapped %s", clazz.getSimpleName(), method.getName(), requestMapping.value());
                    System.out.println(format);
                }
            }
        }
    }
}
